package com.nulldev.util.scripting.cup.vee.utils;

import java.math.BigInteger;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.scripting.cup.vee.CUPRegister;
import static com.nulldev.util.scripting.cup.vee.utils.VEEConstants.*;

public class VEEOperationManager {

	/**
	 * TODO: - Add a autoConvertToType(CUPRegister, Object) to reduce code mess.
	 */

	public static enum VEEOperationType {
		/* Type 0 = Single Operation */

		ADD0,
		SUB0,
		MUL0,
		DIV0,
		MOD0,
		FMA0,

		/* Type 1 = Logic Operations */

		NAND1,
		AND1,
		OR1,
		XOR1,
		NOT1,
	}

	@SuppressWarnings("rawtypes")
	public static Object autoConvertToCompatType(final CUPRegister register, final Object obj) {
		switch (register.getRegisterType()) {
			case CUSTOM: {
				if (register.isSameType(obj))
					return obj;
				throw new UnsupportedOperationException("Cannot automatically convert to compatible type: " + obj);
			}
			case SHORT: {
				if (obj instanceof Short) {
					return (short) obj;
				} else if (obj instanceof Integer) {
					if (isWithinBounds(register.getRegisterType(), (int) obj)) {
						return (short) (int) obj;
					}
				} else {
					throw new UnsupportedOperationException("Cannot automatically convert to compatible type: " + obj);
				}
			}
			case NORMAL: {
				if (obj instanceof Integer) {
					return (int) obj;
				} else if (obj instanceof Short) {
					return (int) (short) obj;
				} else if (obj instanceof String) {
					return MathUtil.autoConvertToInt(obj);
				} else if (obj instanceof CUPRegister<?>) {
					final CUPRegister<?> c = (CUPRegister<?>) obj;
					if (c.isSameAs(register)) {
						return c.get();
					} else {
						throw new UnsupportedOperationException("Cannot automatically convert to compatible type: " + c);
					}
				} else {
					throw new UnsupportedOperationException("Cannot automatically convert to compatible type: " + obj);
				}
			}
			default:
				if (register.isSameType(obj))
					return obj;
				throw new UnsupportedOperationException("Cannot automatically convert to compatible type: " + obj);
		}
	}

	@SuppressWarnings("rawtypes")
	public static void checkIfCompatTypes(final CUPRegister register, final Object obj) {
		if (register == null)
			throw new NullPointerException("register == null!");
		if (obj == null)
			throw new NullPointerException("obj == null!");
		if (!register.isSameType(obj))
			throw new UnsupportedOperationException("Invalid type for register: " + register);
	}

	public static void doOperation(final VEEOperationType operation, @SuppressWarnings("rawtypes") final CUPRegister reg, final Object _append) {
		doOperation(operation, reg, null, _append);
	}

	@SuppressWarnings(
		{ "unchecked", "rawtypes" })
	public static void doOperation(final VEEOperationType operation, final CUPRegister reg, final CUPRegister _output, final Object _append) {
		final CUPRegister output = _output != null ? _output : reg;
		final Object append = autoConvertToCompatType(reg, _append);
		op: {
			switch (operation) {
				/* Type 0 (Standard, Single OP Mathematical Operations */
				case ADD0: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) + ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) + ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) + ((long) areg.get()));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).add(((BigInteger) areg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) + ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) + ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) + ((long) append)));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).add(((BigInteger) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case SUB0: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) - ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) - ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) - ((long) areg.get()));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).subtract(((BigInteger) areg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) - ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) - ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) - ((long) append)));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).subtract(((BigInteger) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case MUL0: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) * ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) * ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) * ((long) areg.get()));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).multiply(((BigInteger) areg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) * ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) * ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) * ((long) append)));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).multiply(((BigInteger) append)));
								break op;
							}
							case FLOAT:
							case FLOAT_SFP: {
								output.set((float) (((float) reg.get()) * ((float) append)));
								break op;
							}
							case DOUBLE: {
								output.set((double) (((double) reg.get()) * ((double) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case DIV0: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						if ((areg.get() instanceof Short && ((short) areg.get()) == 0) || (areg.get() instanceof Integer && ((int) areg.get()) == 0)
								|| (areg.get() instanceof Long && ((long) areg.get()) == 0)
								|| (areg.get() instanceof BigInteger && ((BigInteger) areg.get()).longValue() == 0)) {
							throw new IllegalArgumentException("Operation would result in division by zero!");
						}
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) / ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) / ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) / ((long) areg.get()));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).divide(((BigInteger) areg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						if ((append instanceof Short && ((short) append) == 0) || (append instanceof Integer && ((int) append) == 0)
								|| (append instanceof Long && ((long) append) == 0)
								|| (append instanceof BigInteger && ((BigInteger) append).longValue() == 0)) {
							throw new IllegalArgumentException("Operation would result in division by zero!");
						}
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) / ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) / ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) / ((long) append)));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).divide(((BigInteger) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case MOD0: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) % ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) % ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) % ((long) areg.get()));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).mod(((BigInteger) areg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) % ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) % ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) % ((long) append)));
								break op;
							}
							case EXTENDED: {
								output.set(((BigInteger) reg.get()).mod(((BigInteger) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				/* Type 1 (Binary Operations) */
				case NAND1: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(~((short) reg.get()) & ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(~((int) reg.get()) & ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(~((long) reg.get()) & ((long) areg.get()));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(~(short) (((short) reg.get()) & ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set(~(int) (((int) reg.get()) & ((int) append)));
								break op;
							}
							case WIDE: {
								output.set(~(long) (((long) reg.get()) & ((long) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case AND1: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) & ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) & ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) & ((long) areg.get()));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) & ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) & ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) & ((long) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case OR1: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) | ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) | ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) | ((long) areg.get()));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) | ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) | ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) | ((long) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case XOR1: {
					if (append instanceof CUPRegister<?>) {
						final CUPRegister areg = (CUPRegister) append;
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(((short) reg.get()) ^ ((short) areg.get()));
								break op;
							}
							case NORMAL: {
								output.set(((int) reg.get()) ^ ((int) areg.get()));
								break op;
							}
							case WIDE: {
								output.set(((long) reg.get()) ^ ((long) areg.get()));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) (((short) reg.get()) ^ ((short) append)));
								break op;
							}
							case NORMAL: {
								output.set((int) (((int) reg.get()) ^ ((int) append)));
								break op;
							}
							case WIDE: {
								output.set((long) (((long) reg.get()) ^ ((long) append)));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				case NOT1: {
					if (append instanceof CUPRegister<?>) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set(~((short) reg.get()));
								break op;
							}
							case NORMAL: {
								output.set(~((int) reg.get()));
								break op;
							}
							case WIDE: {
								output.set(~((long) reg.get()));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else if (append instanceof Number) {
						switch (reg.getRegisterType()) {
							case SHORT: {
								output.set((short) ((~(short) reg.get())));
								break op;
							}
							case NORMAL: {
								output.set((int) (~((int) reg.get())));
								break op;
							}
							case WIDE: {
								output.set((long) (~((long) reg.get())));
								break op;
							}
							case CUSTOM:
							default: {
								throw new UnsupportedOperationException("Cannot perform action on CUSTOM register.");
							}
						}
					} else
						throw new IllegalArgumentException("Unknown append object: " + append);
				}
				default:
					throw new UnsupportedOperationException("Unsupported operation: " + operation);
			}
		}
	}
}
